use std::collections::HashMap;
use std::io::{Result,Write};

#[derive(Debug,PartialEq,Clone)]
pub struct HttpResponse<'a>{
    version:&'a str,
    status_code:&'a str,
    status_text:&'a str,
    headers:Option<HashMap<&'a str,&'a str>>,
    body:Option<String>,
}
impl<'a>Default for HttpResponse<'a>{
    fn default()->Self{
        Self{
            version: "HTTP/1.1",
            status_code:"200",
            status_text: "OK",
            headers: None,
            body: None,
        }
    }
}

impl <'a> From<HttpResponse<'a>>  for String {
    fn from(res: HttpResponse<'a>) -> String {
        let res1 = res.clone();
        format!("{} {} {}\r\n{}Content-Length: {}\r\n\r\n{}",
                &res1.version,&res1.status_code,&res1.status_text,&res1.headers(),&res1.body.clone().unwrap().len(),&res1.body())
    }
}

impl<'a>HttpResponse<'a> {
    pub fn new(
        status_code: &'a str,
        headers: Option<HashMap<&'a str, &'a str>>,
        body: Option<String>
    ) -> HttpResponse<'a> {
        let mut response: HttpResponse<'a> = HttpResponse::default();
        if status_code != "200" {
            response.status_code = status_code.into();
        };
        response.headers = match &headers {
            Some(_h) => headers,
            None =>{
                let mut h = HashMap::new();
                h.insert("Content-Type","text/html");
                Some(h)
            }
        };
        response.status_text= match response.status_code {
            "200" =>"OK".into(),
            "400"=>"Bad Request".into(),
            "404"=>"Not Found".into(),
            "500"=>"Internal Server Error".into(),
            _=>"Not Found".into(),
        };
        response.body = body;
        response
    }
    pub fn send_response(&self,write_stream: & mut impl Write) -> Result<()>{
        let res = self.clone();
        let response_string :String = String::from(res);
        let _ = write!(write_stream,"{}",response_string);
        Ok(())
    }

    pub fn version(&self) -> &'a str {
        self.version
    }
    pub fn status_code(&self) -> &'a str {
        self.status_code
    }
    pub fn status_text(&self) -> &'a str {
        self.status_text
    }
    fn headers(&self) ->  String {
        let map:HashMap<&str,&str> = self.headers.clone().unwrap();
        let mut header_string :String = "".into();
        for (k,v) in map.iter(){
            header_string = format!("{}{}:{}\r\n",header_string,k,v);
        }
        header_string
    }
    pub fn body(&self) -> &str{
        match &self.body {
            Some(b)=>b.as_str(),
            None=>"",
        }
    }
}
#[cfg(test)]
mod test{
 use super::*;
    #[test]
    fn test_response_struct_creation_200(){
        let response_actual = HttpResponse::new("200",None,Some("xxx".into()),);
        let response_expected = HttpResponse{version:"HTTP/1.1",status_text:"OK",status_code:"200",
            headers:{
                let mut h = HashMap::new();
                h.insert("Content-Type","text/html");
                Some(h)
            },
            body:Some("xxx".into()),
        };
        assert_eq!(response_actual,response_expected);
    }
}